package tech

import (
	"fmt"
	"regexp"
	"slices"
	"ssp/set"
	"strings"
)

func merge[T any](r [][]T, x T) [][]T {
	if len(r) == 0 {
		return [][]T{{x}}
	}
	for i, xs := range r {
		r[i] = append([]T{x}, xs...)
	}
	return r
}

func combine[T any](xs []T, n int) [][]T {
	if n < 1 {
		return [][]T{}
	}
	if n == 1 {
		r := [][]T{}
		for _, x := range xs {
			r = append(r, []T{x})
		}
		return r
	}
	length := len(xs)
	if length < n {
		return [][]T{}
	}
	if length == n {
		return [][]T{xs}
	}
	return append(merge(combine(xs[1:], n-1), xs[0]), combine(xs[1:], n)...)
}

func allCombine[T any](xs []T) [][]T {
	r := [][]T{}
	for i := 1; i <= len(xs); i++ {
		r = append(r, combine(xs, i)...)
	}
	return r
}

func (g *Grid) doStrategy(doStrategyHouse func(House) (bool, Result)) (used bool, result Result) {
	for _, box := range g.boxes {
		if used, result = doStrategyHouse(box); used {
			return
		}
	}

	for _, row := range g.rows {
		if used, result = doStrategyHouse(row); used {
			return
		}

	}

	for _, col := range g.cols {
		if used, result = doStrategyHouse(col); used {
			return
		}
	}
	return
}

func (g *Grid) Validate() bool {
	if g.solvedCount != 81 {
		return false
	}

	for _, row := range g.rows {
		if !validateHouse(row) {
			return false
		}
	}

	for _, col := range g.cols {
		if !validateHouse(col) {
			return false
		}
	}

	for _, box := range g.boxes {
		if !validateHouse(box) {
			return false
		}
	}
	return true
}

func validateHouse(house House) (ok bool) {
	ok = true

	if house.solvedCount != 9 {
		ok = false
	}

	values := set.NewSet()
	for _, cell := range house.cells {
		if 1 <= cell.value && cell.value <= 9 {
			values.Add(toCand(cell.value))
		}
	}

	if values != set.NewSet(0, 1, 2, 3, 4, 5, 6, 7, 8) {
		ok = false
	}

	return
}

func (g *Grid) seenCells(c *Cell) []*Cell {
	cells := []*Cell{}
	for _, cell := range g.boxes[c.boxNum].cells {
		if !cell.Equal(c) {
			cells = append(cells, cell)
		}
	}
	for _, cell := range g.rows[c.rowNum].cells {
		if cell.boxNum != c.boxNum {
			cells = append(cells, cell)
		}
	}
	for _, cell := range g.cols[c.colNum].cells {
		if cell.boxNum != c.boxNum {
			cells = append(cells, cell)
		}
	}
	return cells
}

func (g *Grid) cellsSeenCells(cs []*Cell) []*Cell {
	shares := g.seenCells(cs[0])
	for _, cell := range cs[1:] {
		shares = shareCells(shares, g.seenCells(cell))
	}
	return shares
}

func (g *Grid) candSeenCells(cand int, cell *Cell) (seens []*Cell) {
	for _, cell := range g.seenCells(cell) {
		if cell.cands.Has(cand) {
			seens = append(seens, cell)
		}
	}
	return
}

func (g *Grid) candCellsSeenCells(cand int, cs []*Cell) []*Cell {
	shares := g.candSeenCells(cand, cs[0])
	for _, cell := range cs[1:] {
		shares = shareCells(shares, g.candSeenCells(cand, cell))
	}
	return shares
}

func shareCells(cs1 []*Cell, cs2 []*Cell) []*Cell {
	shared := make([]*Cell, 0)
	for _, c1 := range cs1 {
		for _, c2 := range cs2 {
			if c1.Equal(c2) {
				shared = append(shared, c1)
			}
		}
	}
	return shared
}

func chooseHasCandCells(house *House, cand int) (cells []*Cell) {
	for _, cell := range house.cells {
		if cell.cands.Has(cand) {
			cells = append(cells, cell)
		}
	}
	return
}

func getUnsolvedCells(cells []*Cell) (unsolvedCells []*Cell) {
	for _, cell := range cells {
		if !cell.solved() {
			unsolvedCells = append(unsolvedCells, cell)
		}
	}
	return
}

/*
	func (g *Grid) getSameHouses(x, y *Cell) (houses []House) {
		if x.rowNum == y.rowNum {
			houses = append(houses, g.rows[x.rowNum])
		}
		if x.colNum == y.colNum {
			houses = append(houses, g.cols[x.colNum])
		}
		if x.boxNum == y.boxNum {
			houses = append(houses, g.boxes[x.boxNum])
		}
		return
	}
*/
func (g *Grid) getSameHouses(cells []*Cell) (houses []House) {
	x := cells[0]
	flag := true
	for _, y := range cells[1:] {
		if x.rowNum != y.rowNum {
			flag = false
			break
		}
	}
	if flag {
		houses = append(houses, g.rows[x.rowNum])
	}
	flag = true
	for _, y := range cells[1:] {
		if x.colNum != y.colNum {
			flag = false
			break
		}
	}
	if flag {
		houses = append(houses, g.cols[x.colNum])
	}
	flag = true
	for _, y := range cells[1:] {
		if x.boxNum != y.boxNum {
			flag = false
			break
		}
	}
	if flag {
		houses = append(houses, g.boxes[x.boxNum])
	}
	return
}

func (g *Grid) getSameLine(cells []*Cell) (houses *House) {
	x := cells[0]
	flag := true
	for _, y := range cells[1:] {
		if x.rowNum != y.rowNum {
			flag = false
			break
		}
	}
	if flag {
		return &g.rows[x.rowNum]
	}
	flag = true
	for _, y := range cells[1:] {
		if x.colNum != y.colNum {
			flag = false
			break
		}
	}
	if flag {
		return &g.cols[x.colNum]
	}
	return nil
}

func (g *Grid) useTechInAllHouse(techHouse func(*House) (bool, []*Result)) (used bool, results []*Result) {
	for _, house := range g.houses {
		if ok, rs := techHouse(house); ok {
			used = true
			results = append(results, rs...)
		}
	}

	return
}

func decodeCands(s string) (cs []string) {
	// Define a regular expression to match the pattern R<row>C<col>[<cands>]
	re := regexp.MustCompile(`R(\d+)C(\d+)([\[{\(])([\d]+)([\]}\)])`)

	// Find matches in the input string
	matches := re.FindStringSubmatch(s)
	if len(matches) != 6 {
		return // No valid match found
	}

	rows := matches[1]
	cols := matches[2]
	openBracket := matches[3][0]  // Extract the first character of the bracket
	closeBracket := matches[5][0] // Extract the first character of the bracket
	cands := matches[4]

	// Generate all combinations of row, column, and candidate values
	for _, r := range rows {
		for _, c := range cols {
			for _, cand := range cands {
				cs = append(cs, fmt.Sprintf("R%cC%c%c%c%c", r, c, openBracket, cand, closeBracket))
			}
		}
	}

	return cs
}

func ContainRemoved(results []*Result, removed ...string) bool {
	gots := []string{}
	for _, r := range removed {
		gots = append(gots, decodeCands(r)...)
	}
	wants := []string{}
	for _, result := range results {
		for _, r := range result.Removed {
			wants = append(wants, decodeCands(r)...)
		}
	}
	for _, got := range gots {
		if !slices.Contains(wants, got) {
			return false
		}
	}

	return true
}

func ContainRemovedPlus(results []*Result, removed ...string) (ok bool, notFounds []string) {
	ok = true
	for _, rr := range removed {
		found := false
		for _, result := range results {
			for _, r := range result.Removed {
				if r[:4] == rr[:4] && strings.Contains(r[5:len(r)-1], rr[5:len(rr)-1]) {
					found = true
				}
			}
		}
		if !found {
			notFounds = append(notFounds, rr)
			ok = false
		}
	}
	return
}

func ContainChosen(results []*Result, chosen ...string) bool {
	for _, v := range chosen {
		found := false
		for _, result := range results {
			if v == result.Chosen[0] {
				found = true
			}
		}
		if !found {
			return false
		}
	}
	return true
}

func zip[T any](a ...[]T) [][]T {
	n := len(a)
	if n == 1 {
		r := make([][]T, 0)
		for _, x := range a[0] {
			r = append(r, []T{x})
		}
		return r
	}
	r := make([][]T, 0)
	zs := zip(a[:n-1]...)
	for _, x := range a[n-1] {
		for _, z := range zs {
			r = append(r, append(z, x))
		}
	}
	return r
}

func (g *Grid) canRemoveSeenCandsInPath(cands []int, cells, path []*Cell) (used bool, rmCells []*Cell) {
	shares := g.seenCells(cells[0])
	for _, cell := range cells[1:] {
		shares = shareCells(shares, g.seenCells(cell))
	}

	for _, cell := range shares {
		if !slices.Contains(path, cell) && cell.canRemoveCands(set.NewSet(cands...)) {
			rmCells = append(rmCells, cell)
			used = true
		}
	}
	return
}

func (g *Grid) canRemoveSeenCandInPath(cand int, cells, path []*Cell) (used bool, rmCells []*Cell) {
	return g.canRemoveSeenCandsInPath([]int{cand}, cells, path)
}

func (g *Grid) findCandAllCells() [][]*Cell {
	all := [9][]*Cell{}
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if cell.solved() {
				continue
			}
			for _, cand := range cell.cands.GetList() {
				all[cand] = append(all[cand], cell)
			}
		}
	}
	return all[:]
}

func isOnlyInHouses(cand int, cells []*Cell, houses []House) bool {
	for _, house := range houses {
		ok := true
		for _, cell := range house.cells {
			if slices.Contains(cells, cell) {
				continue
			}
			if cell.cands.Has(cand) {
				ok = false
				break
			}
		}
		if ok {
			return true
		}
	}
	return false
}

func PrintResults(results []*Result) {
	for _, r := range results {
		fmt.Println(r)
	}
}

func FoundChosen(chosen string, results []*Result) bool {
	found := false
	for _, r := range results {
		if r.Chosen[0] == chosen {
			found = true
		}
	}
	return found
}

func unionCellCands(cells []*Cell) (allCands set.Set) {
	for _, cell := range cells {
		allCands = allCands.Union(cell.cands)
	}
	return
}

func isSameHouse(cs []*Cell) bool {
	rowNums := set.NewSet()
	colNums := set.NewSet()
	boxNums := set.NewSet()
	for _, c := range cs {
		rowNums.Add(c.rowNum)
		colNums.Add(c.colNum)
		boxNums.Add(c.boxNum)
	}
	return rowNums.Count() == 1 || colNums.Count() == 1 || boxNums.Count() == 1

}
